﻿// <copyright file="MultiScaleImage.Extensions.cs" company="Microsoft Corporation">
// Copyright (c) 2010 Microsoft Corporation All Rights Reserved
// </copyright>
// <author>Microsoft Live Labs</author>
// <email></email>
// <date>2010-05-15</date>
// <summary>MultiScaleImage extension methods</summary>

namespace Microsoft.LiveLabs.DeepZoom
{
    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media.Animation;

    /// <summary>
    /// Deep Zoom arange mode
    /// </summary>
    public enum ArrangeMode
    {
        /// <summary>
        /// Square arrangement
        /// </summary>
        Square,

        /// <summary>
        /// Tight arrangement
        /// </summary>
        Tight,

        /// <summary>
        /// Spiral arrangement
        /// </summary>
        Spiral,

        /// <summary>
        /// Filmstrip arrangement
        /// </summary>
        Filmstrip,
    }

    /// <summary>
    /// Neighbors for the sub image
    /// </summary>
    public class Neighbors
    {
        /// <summary>
        /// Gets or sets the neighbor above
        /// </summary>
        public MultiScaleSubImage Up { get; set; }

        /// <summary>
        /// Gets or sets the neighbor below
        /// </summary>
        public MultiScaleSubImage Down { get; set; }

        /// <summary>
        /// Gets or sets the left neighbor
        /// </summary>
        public MultiScaleSubImage Left { get; set; }

        /// <summary>
        /// Gets or sets teh right neighbor
        /// </summary>
        public MultiScaleSubImage Right { get; set; }
    }

    /// <summary>
    /// MultiScale image extensions
    /// </summary>
    public static class MultiScaleImageExtensions
    {
        #region Fields
        /// <summary>
        /// Clamp Margin
        /// </summary>
        private const double ClampMargin = .1;

        private const int SquareMargin = 15;
        private const int SquareItemSize = 128;

        private const int TightMargin = 25;
        private const int TightItemSize = 128;

        private const int SpiralItemSize = 128;

        private const int FilmstripMargin = 15;
        private const int FilmstripItemSize = 128;
        #endregion

        #region Behaviors

        ////public static void ApplyDefaultBehaviors( this MultiScaleImage msi )
        ////{
        ////    new DeepZoomBehaviors( msi, false );
        ////    _multiScaleImageAspectRatio = GetAspectRatio(msi);
        ////}

        ////public static void ApplyMouseWheelBehavior( this MultiScaleImage msi )
        ////{
        ////    new DeepZoomBehaviors( msi, true );
        ////}

        #endregion

        #region Scene

        /// <summary>
        /// Get the viewport bounds
        /// </summary>
        /// <param name="msi">the multiscale image</param>
        /// <returns>the bounding rectangle</returns>
        public static Rect GetViewportBounds(this MultiScaleImage msi)
        {
            Point tl = msi.ElementToLogicalPoint(new Point(0, 0));
            Point br = msi.ElementToLogicalPoint(new Point(msi.ActualWidth, msi.ActualHeight));
            return new Rect(tl.X, tl.Y, br.X - tl.X, br.Y - tl.Y);
        }

        /// <summary>
        /// Set the viewport bounds
        /// </summary>
        /// <param name="msi"></param>
        /// <param name="newBounds"></param>
        public static void SetViewportBounds(this MultiScaleImage msi, Rect newBounds)
        {
            msi.ViewportOrigin = new Point(newBounds.Left, newBounds.Top);
            msi.ViewportWidth = newBounds.Width;
        }

        /// <summary>
        /// Get the Viewport center
        /// </summary>
        /// <param name="multiScaleImage"></param>
        /// <returns>the viewport center</returns>
        public static Point GetViewportCenter(this MultiScaleImage multiScaleImage)
        {
            return new Point(
                multiScaleImage.ViewportOrigin.X + multiScaleImage.ViewportWidth / 2,
                multiScaleImage.ViewportOrigin.Y + multiScaleImage.ViewportWidth / 2 * multiScaleImage.ActualHeight / multiScaleImage.ActualWidth);
        }

        /// <summary>
        /// Set the viewport center
        /// </summary>
        /// <param name="multiScaleImage">the multiscale image</param>
        /// <param name="value">the centerpoint</param>
        public static void SetViewportCenter(this MultiScaleImage multiScaleImage, Point value)
        {
            multiScaleImage.ViewportOrigin = new Point(
                value.X - multiScaleImage.ViewportWidth / 2,
                value.Y - multiScaleImage.ViewportWidth / 2 * multiScaleImage.ActualHeight / multiScaleImage.ActualWidth);
        }

        /// <summary>
        /// Returns a viewport rect which shows the given rect centered in the viewport.
        /// </summary>
        /// <param name="msi">the MultiScaleImage</param>
        /// <param name="rect">the rectange</param>
        /// <returns>a viewport rectangle</returns>
        public static Rect CenterRectInViewport(this MultiScaleImage msi, Rect rect)
        {
            double rectAspect = rect.Width / rect.Height;
            double msiAspect = msi.ActualWidth / msi.ActualHeight;
            if (rectAspect > msiAspect)
            {
                // rect wider than the viewport
                double viewportHeight = rect.Height * rectAspect / msiAspect;
                return new Rect(rect.Left, rect.Top + rect.Height / 2 - viewportHeight / 2, rect.Width, viewportHeight);
            }
            else
            {
                // rect taller than the viewport
                double viewportWidth = rect.Width * msiAspect / rectAspect;
                return new Rect(rect.Left + rect.Width / 2 - viewportWidth / 2, rect.Top, viewportWidth, rect.Height);
            }
        }

        /// <summary>
        /// Animate to a rectangle
        /// </summary>
        /// <param name="multiScaleImage">the multiscale image</param>
        /// <param name="newViewportBounds">the new viewport bounds</param>
        public static void AnimateToRect(this MultiScaleImage multiScaleImage, Rect newViewportBounds)
        {
            new ViewportAnimation(multiScaleImage, newViewportBounds);
        }

        /// <summary>
        /// Get the scene bounds
        /// </summary>
        /// <param name="msi">the multiscale image</param>
        /// <returns>the scene bounds</returns>
        public static Rect GetSceneBounds(this MultiScaleImage msi)
        {
            if (msi.SubImages.Count == 0)
            {
                return new Rect(0, 0, 1.0, 1.0 / msi.AspectRatio);
            }

            double minX = double.MaxValue;
            double maxX = double.MinValue;
            double minY = double.MaxValue;
            double maxY = double.MinValue;
            foreach (MultiScaleSubImage subImage in msi.SubImages)
            {
                Rect surfaceBounds = subImage.GetBounds();
                minX = Math.Min(minX, surfaceBounds.X);
                maxX = Math.Max(maxX, surfaceBounds.Right);
                minY = Math.Min(minY, surfaceBounds.Y);
                maxY = Math.Max(maxY, surfaceBounds.Bottom);
            }

            return new Rect(minX, minY, maxX - minX, maxY - minY);
        }

        /// <summary>
        /// Get the zoom factor
        /// </summary>
        /// <param name="msi">the multiscale image</param>
        /// <returns>the zoom factor</returns>
        public static double GetZoom1(this MultiScaleImage msi)
        {
            return Math.Log(1 / msi.ViewportWidth, 2);
        }

        ////public static void ZoomToFit( this MultiScaleImage msi )
        ////{
        ////    Rect bounds = msi.GetSceneBounds();
        ////    msi.ZoomToFit( bounds );
        ////}

        ////public static void ZoomToFit( this MultiScaleImage msi, MultiScaleSubImage subImage )
        ////{
        ////    msi.ZoomToFit( subImage.GetBounds() );
        ////}

        ////public static void ZoomToFit( this MultiScaleImage msi, Rect bounds )
        ////{
        ////    double boundsAspectRatio = bounds.Width / bounds.Height;
        ////    double msiAspectRatio = msi.ActualWidth / msi.ActualHeight;
        ////    if( msiAspectRatio > boundsAspectRatio )
        ////    {
        ////        double targetViewportWidth = bounds.Height * msiAspectRatio;
        ////        double targetPointX = ( targetViewportWidth - bounds.Width ) / 2.0;
        ////        msi.ViewportOrigin = new Point( bounds.X - targetPointX, bounds.Y );
        ////        msi.ViewportWidth = targetViewportWidth;
        ////        msi.SetZoom(1 / targetViewportWidth);
        ////    }
        ////    else
        ////    {
        ////        msi.ViewportWidth = bounds.Width;
        ////        msi.SetZoom(1 / bounds.Width);
        ////        double msiHeight = bounds.Width / msiAspectRatio;
        ////        double diff = msiHeight - bounds.Height;
        ////        msi.ViewportOrigin = new Point( bounds.X, bounds.Y - diff / 2.0 );
        ////    }
        ////    // TODO: something to determine zoom from viewportwidth and bounds
        ////    //double newZoom = msi.ActualWidth / bounds.Width;
        ////    //string s = msi.ActualWidth.ToString() + " " + bounds.Width.ToString();
        ////    //MessageBox.Show( s );
        ////    //MultiScaleImageProperties.SetZoom( msi, newZoom );
        ////}

        /*
        public static void BeginZoomIn( this MultiScaleImage msi )
        {
            _zoomTimer = new Timer( delegate( object state )
            {
                this.Dispatcher.BeginInvoke( delegate()
                {
                    double zoom = MultiScaleImageProperties.GetZoom( msi );
                    msi.Zoom( zoom / 1.5, msi.ElementToLogicalPoint( new Point( msi.ActualWidth / 2.0, msi.ActualHeight / 2.0 ) ) );
                } );
            }, null, 0, 100 );
        }

        public static void EndZoomIn( this MultiScaleImage msi )
        {
            if( _zoomTimer != null )
                _zoomTimer.Dispose();
            _zoomTimer = null;
            double zoom = MultiScaleImageProperties.GetZoom( msi );
            msi.Zoom( zoom / 1.3, msi.ElementToLogicalPoint( new Point( msi.ActualWidth / 2.0, msi.ActualHeight / 2.0 ) ) );
        }
         */

        /// <summary>
        /// Clamp the image to the screen
        /// </summary>
        /// <param name="multiScaleImage">the multi scale image</param>
        /// <param name="viewportOrigin">the viewport origin</param>
        /// <returns>the point result</returns>
        internal static Point ClampToScreen(this MultiScaleImage multiScaleImage, Point viewportOrigin)
        {
            double zoom = 1 / multiScaleImage.ViewportWidth;
            double collectionXOffset = 0;
            double collectionYOffset = 0;
            if (multiScaleImage.SubImages.Count > 0)
            {
                Rect daBounds = GetSceneBounds(multiScaleImage);
                collectionXOffset = daBounds.Width - 1;

                collectionYOffset = daBounds.Height - 1;
            }

            // make sure part of the image is on the screen
            double viewportAspect = multiScaleImage.ActualWidth / multiScaleImage.ActualHeight;

            // check for being off the left side
            viewportOrigin.X = Math.Min(viewportOrigin.X, (1 - 1 / zoom * ClampMargin) + collectionXOffset);

            // check for being off the top
            viewportOrigin.Y = Math.Min(viewportOrigin.Y, (1 / multiScaleImage.AspectRatio - 1 / zoom * ClampMargin / viewportAspect) + collectionYOffset);

            // check for being off the right side
            viewportOrigin.X = Math.Max(viewportOrigin.X, -1 / zoom * (1 - ClampMargin));

            // check for being off the bottom
            viewportOrigin.Y = Math.Max(viewportOrigin.Y, 1 / zoom * ClampMargin / viewportAspect - 1 / zoom / viewportAspect);

            return viewportOrigin;
        }

        private static double GetAspectRatio(this MultiScaleImage multiScaleImage)
        {
            double returnValue = multiScaleImage.AspectRatio;

            if (multiScaleImage.SubImages.Count > 0)
            {
                double mostTopY = 0;
                double mostBottomY = 0;
                double mostLeftX = 0;
                double mostRightX = 0;
                double mostRightWidth = 0;
                double mostBottomHeight = 0;

                foreach (MultiScaleSubImage subImageItem in multiScaleImage.SubImages)
                {
                    if (subImageItem.ViewportOrigin.Y > mostTopY)
                    {
                        mostBottomY = subImageItem.ViewportOrigin.Y;
                        mostBottomHeight = subImageItem.ViewportWidth / subImageItem.AspectRatio;
                    }

                    if (subImageItem.ViewportOrigin.Y < mostBottomY)
                    {
                        mostTopY = subImageItem.ViewportOrigin.Y;
                    }

                    if (subImageItem.ViewportOrigin.X < mostRightX)
                    {
                        mostRightX = subImageItem.ViewportOrigin.X;
                        mostRightWidth = subImageItem.ViewportWidth;
                    }

                    if (subImageItem.ViewportOrigin.X > mostLeftX)
                    {
                        mostLeftX = subImageItem.ViewportOrigin.X;
                    }
                }

                double width = Math.Abs(mostLeftX - mostRightX) + mostRightWidth;
                double height = Math.Abs(mostTopY - mostBottomY) + mostBottomHeight;
                returnValue = width / height;
            }

            return returnValue;
        }

        ////public static void Zoom( this MultiScaleImage msi, double newZoom, Point origin )
        ////{
        ////    double minimumZoom = MultiScaleImageProperties.GetMinimumZoom(msi);
        ////    double maximumZoom = MultiScaleImageProperties.GetMaximumZoom(msi);
        ////    newZoom = Math.Min(Math.Max(minimumZoom, newZoom), maximumZoom);
        ////    double zoom = MultiScaleImageProperties.GetZoom(msi);

        ////    msi.ZoomAboutLogicalPoint(newZoom / zoom, origin.X, origin.Y);
        ////    msi.SetZoom(newZoom);
        ////}

        #endregion

        #region Arrangement

        /// <summary>
        /// Does this MSI have an arrangement?
        /// </summary>
        /// <param name="msi">the MultiScale Image</param>
        /// <returns>true if the MSI has an arrangement</returns>
        public static bool HasArrangement(this MultiScaleImage msi)
        {
            if (msi.SubImages.Count <= 1)
            {
                return true;
            }

            // Hacky check - if more than 1 image is at 0,0, assume none are arranged
            Point zero = new Point();
            foreach (MultiScaleSubImage subImage in msi.SubImages)
            {
                if (subImage.ViewportOrigin != zero)
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Get the current arrangement
        /// </summary>
        /// <param name="msi">the multiscale image</param>
        /// <returns>the rectangles</returns>
        public static Rect[] GetCurrentArrangement(this MultiScaleImage msi)
        {
            Rect[] rects = new Rect[msi.SubImages.Count];
            for (int n = 0; n < msi.SubImages.Count; n++)
            {
                rects[n] = msi.SubImages[n].GetBounds();
            }

            return rects;
        }

        /// <summary>
        /// Set the arrangement
        /// </summary>
        /// <param name="msi">the multiscale image</param>
        /// <param name="rects">the rectangles</param>
        public static void SetArrangement(this MultiScaleImage msi, Rect[] rects)
        {
            if (rects.Length != msi.SubImages.Count)
            {
                throw new ArgumentOutOfRangeException("rects");
            }

            for (int n = 0; n < rects.Length; n++)
            {
                msi.SubImages[n].SetBounds(rects[n]);
            }
        }

        ////public static Rect ArrangeSubImages( this MultiScaleImage msi, ArrangeMode mode )
        ////{
        ////    return msi.ArrangeSubImages( mode, true );
        ////}

        ////public static Rect ArrangeSubImages( this MultiScaleImage msi, ArrangeMode mode, bool animated )
        ////{
        ////    if( msi.SubImages.Count == 0 )
        ////        return Rect.Empty;

        ////    DeepZoomAnimationBuilder animationBuilder = null;
        ////    if( animated == true )
        ////    {
        ////        animationBuilder = new DeepZoomAnimationBuilder();
        ////        animationBuilder.Begin( TimeSpan.FromMilliseconds( 1000.0 ) );
        ////    }

        ////    double minX = double.MaxValue;
        ////    double maxX = double.MinValue;
        ////    double minY = double.MaxValue;
        ////    double maxY = double.MinValue;
        ////    switch( mode )
        ////    {
        ////        case ArrangeMode.Square:
        ////            {
        ////                double width = ( double )( SquareItemSize * Math.Ceiling( Math.Sqrt( msi.SubImages.Count ) ) );
        ////                int itemsPerRow = ( int )( width / SquareItemSize );
        ////                for( int n = 0; n < msi.SubImages.Count; n++ )
        ////                {
        ////                    MultiScaleSubImage subImage = msi.SubImages[ n ];

        ////                    int column = n % itemsPerRow;
        ////                    int row = n / itemsPerRow;

        ////                    Rect bounds = Rect.Empty;
        ////                    bounds.X = column * ( SquareItemSize + SquareMargin );
        ////                    bounds.Y = row * ( SquareItemSize + SquareMargin );

        ////                    double px = 0.0;
        ////                    double py = 0.0;
        ////                    if( subImage.AspectRatio > 1.0 )
        ////                    {
        ////                        bounds.Height = ( 1.0 / subImage.AspectRatio ) * SquareItemSize;
        ////                        bounds.Width = SquareItemSize;
        ////                    }
        ////                    else
        ////                    {
        ////                        bounds.Width = subImage.AspectRatio * SquareItemSize;
        ////                        bounds.Height = SquareItemSize;
        ////                    }
        ////                    px = SquareItemSize - bounds.Width;
        ////                    py = SquareItemSize - bounds.Height;
        ////                    bounds.X += px / 2.0;
        ////                    bounds.Y += py / 2.0;
        ////                    if( column > 0 )
        ////                        bounds.X += SquareMargin / 2.0;
        ////                    if( row > 0 )
        ////                        bounds.Y += SquareMargin / 2.0;

        ////                    if( animated == true )
        ////                        animationBuilder.AnimateBounds( subImage, bounds );
        ////                    else
        ////                        subImage.SetBounds( bounds );
        ////                    minX = Math.Min( minX, bounds.X );
        ////                    maxX = Math.Max( maxX, bounds.Right );
        ////                    minY = Math.Min( minY, bounds.Y );
        ////                    maxY = Math.Max( maxY, bounds.Bottom );
        ////                }
        ////            }
        ////            break;

        ////        case ArrangeMode.Tight:
        ////            {
        ////                double width = ( ( TightItemSize * 1.3 + TightMargin ) * Math.Ceiling( Math.Sqrt( msi.SubImages.Count ) ) );
        ////                Point rowOrigin = new Point();
        ////                double rowWidth = 0.0f;
        ////                double rowHeight = 999999.0f;
        ////                int rowStart = -1;
        ////                for( int n = 0; n < msi.SubImages.Count; n++ )
        ////                {
        ////                    MultiScaleSubImage subImage = msi.SubImages[ n ];
        ////                    Rect bounds = Rect.Empty;
        ////                    bounds.Width = subImage.AspectRatio * TightItemSize;
        ////                    bounds.Height = TightItemSize;
        ////                    if( ( rowWidth + bounds.Width + TightMargin > width ) &&
        ////                        ( rowStart != -1 ) )
        ////                    {
        ////                        // End of previous row
        ////                        double rowScale = width / ( rowWidth - TightMargin );
        ////                        double rowar = rowHeight / rowWidth;
        ////                        rowWidth = width;
        ////                        rowHeight = rowar * width;
        ////                        double maxHeight = rowHeight;
        ////                        Point origin = rowOrigin;
        ////                        for( int m = rowStart; m < n; m++ )
        ////                        {
        ////                            MultiScaleSubImage rowSubImage = msi.SubImages[ m ];
        ////                            Rect rowItemBounds = rowSubImage.GetBounds();
        ////                            rowItemBounds.X = origin.X;
        ////                            rowItemBounds.Y = origin.Y;
        ////                            rowItemBounds.Width = rowSubImage.AspectRatio * TightItemSize * rowScale;
        ////                            rowItemBounds.Height = TightItemSize * rowScale;
        ////                            maxHeight = Math.Max( rowItemBounds.Height, maxHeight );
        ////                            if (animated == true)
        ////                            {
        ////                                Rect tempRowBounds = rowItemBounds;
        ////                                tempRowBounds.Width /= width;
        ////                                tempRowBounds.X /= width;
        ////                                tempRowBounds.Y /= width;
        ////                                animationBuilder.AnimateBounds( rowSubImage, tempRowBounds);
        ////                            }
        ////                            else
        ////                            {
        ////                                rowSubImage.SetBounds(rowItemBounds);
        ////                            }
        ////                            minX = Math.Min( minX, rowItemBounds.X );
        ////                            maxX = Math.Max( maxX, rowItemBounds.Right );
        ////                            minY = Math.Min( minY, rowItemBounds.Y );
        ////                            maxY = Math.Max( maxY, rowItemBounds.Bottom );
        ////                            origin.X += rowItemBounds.Width + TightMargin * rowScale;
        ////                        }
        ////                        rowOrigin.Y += maxHeight + TightMargin;
        ////                        rowWidth = 0.0f;
        ////                        rowHeight = 999999.0f;
        ////                        rowStart = -1;
        ////                    }

        ////                    // Add to row
        ////                    if( rowStart == -1 )
        ////                        rowStart = n;
        ////                    rowWidth += bounds.Width + TightMargin;
        ////                    rowHeight = Math.Min( rowHeight, bounds.Height );
        ////                }
        ////                if( rowStart != -1 )
        ////                {
        ////                    // Finish up last row
        ////                    double rowScale = 1.0f;
        ////                    Point origin = rowOrigin;
        ////                    for( int m = rowStart; m < msi.SubImages.Count; m++ )
        ////                    {
        ////                        MultiScaleSubImage rowSubImage = msi.SubImages[ m ];
        ////                        Rect rowItemBounds = rowSubImage.GetBounds();
        ////                        rowItemBounds.X = origin.X;
        ////                        rowItemBounds.Y = origin.Y;
        ////                        rowItemBounds.Width = rowSubImage.AspectRatio * TightItemSize * rowScale;
        ////                        rowItemBounds.Height = TightItemSize * rowScale;

        ////                        Rect tempRowBounds = rowItemBounds;
        ////                        tempRowBounds.Width /= width;
        ////                        tempRowBounds.X /= width;
        ////                        tempRowBounds.Y /= width;
        ////                        if (animated == true)
        ////                        {
        ////                            animationBuilder.AnimateBounds(rowSubImage, tempRowBounds);
        ////                        }
        ////                        else
        ////                        {
        ////                            rowSubImage.SetBounds(tempRowBounds);
        ////                        }
        ////                        minX = Math.Min( minX, rowItemBounds.X );
        ////                        maxX = Math.Max( maxX, rowItemBounds.Right );
        ////                        minY = Math.Min( minY, rowItemBounds.Y );
        ////                        maxY = Math.Max( maxY, rowItemBounds.Bottom );
        ////                        origin.X += rowItemBounds.Width + TightMargin * rowScale;
        ////                    }
        ////                }
        ////            }
        ////            break;
        ////        case ArrangeMode.Spiral:
        ////            {
        ////                double angle = 0;
        ////                double cellRadius = msi.ActualHeight / ( 2.0 * Math.Sqrt( 600.0 ) );//arrangement.Count));

        ////                double distanceIncrement = cellRadius * 0.5;
        ////                double radius = 0.6 * distanceIncrement;

        ////                int n = 0;
        ////                foreach( MultiScaleSubImage subImage in msi.SubImages )
        ////                {
        ////                    angle += GetIncrementAngle( radius, distanceIncrement, cellRadius );
        ////                    radius += distanceIncrement / ( Math.Sqrt( radius ) - Math.Sqrt( distanceIncrement / 2.0 ) );

        ////                    double itemWidth;
        ////                    double itemHeight;
        ////                    if( subImage.AspectRatio > 1.0 )
        ////                    {
        ////                        itemHeight = ( 1.0 / subImage.AspectRatio ) * SpiralItemSize;
        ////                        itemWidth = SpiralItemSize;
        ////                    }
        ////                    else
        ////                    {
        ////                        itemWidth = subImage.AspectRatio * SpiralItemSize;
        ////                        itemHeight = SpiralItemSize;
        ////                    }

        ////                    double ratio = Math.Sqrt( 600.0 / msi.SubImages.Count );
        ////                    Point p = new Point( Math.Cos( angle ) * radius * ratio, Math.Sin( angle ) * radius * ratio ); //center point for next image
        ////                    double rad = Math.Sqrt( itemWidth * itemWidth + itemHeight * itemHeight ) / ratio;
        ////                    Rect bounds = new Rect();
        ////                    bounds.Width = ( itemWidth * cellRadius / rad );
        ////                    bounds.Height = ( itemHeight * cellRadius / rad );
        ////                    bounds.X = (p.X - bounds.Width / 2.0) + 450;
        ////                    bounds.Y = (p.Y - bounds.Height / 2.0) + 450;

        ////                    Rect tempRowBounds = bounds;
        ////                    tempRowBounds.Width /= 1000;
        ////                    tempRowBounds.X /= 1000;
        ////                    tempRowBounds.Y /= 1000;
        ////                    if (animated == true)
        ////                    {
        ////                        animationBuilder.AnimateBounds(subImage, tempRowBounds);
        ////                    }
        ////                    else
        ////                    {
        ////                        subImage.SetBounds(bounds);
        ////                    }
        ////                    minX = Math.Min( minX, bounds.X );
        ////                    maxX = Math.Max( maxX, bounds.Right );
        ////                    minY = Math.Min( minY, bounds.Y );
        ////                    maxY = Math.Max( maxY, bounds.Bottom );

        ////                    n++;
        ////                }
        ////            }
        ////            break;
        ////        case ArrangeMode.Filmstrip:
        ////            {
        ////                double x = 0.0;
        ////                int n = 0;
        ////                foreach( MultiScaleSubImage subImage in msi.SubImages )
        ////                {
        ////                    Rect bounds = Rect.Empty;
        ////                    bounds.X = x;
        ////                    bounds.Y = 0.0;
        ////                    if( subImage.AspectRatio > 1.0 )
        ////                    {
        ////                        bounds.Height = ( 1.0 / subImage.AspectRatio ) * SquareItemSize;
        ////                        bounds.Width = SquareItemSize;
        ////                    }
        ////                    else
        ////                    {
        ////                        bounds.Width = subImage.AspectRatio * SquareItemSize;
        ////                        bounds.Height = SquareItemSize;
        ////                    }
        ////                    bounds.Y += ( FilmstripItemSize - bounds.Height ) / 2.0;

        ////                    Rect tempRowBounds = bounds;
        ////                    tempRowBounds.Width /= 1000;
        ////                    tempRowBounds.X /= 1000;
        ////                    tempRowBounds.Y /= 1000;
        ////                    if (animated == true)
        ////                    {
        ////                        animationBuilder.AnimateBounds(subImage, tempRowBounds);
        ////                    }
        ////                    else
        ////                    {
        ////                        subImage.SetBounds(tempRowBounds);
        ////                    }
        ////                    minX = Math.Min( minX, bounds.X );
        ////                    maxX = Math.Max( maxX, bounds.Right );
        ////                    minY = Math.Min( minY, bounds.Y );
        ////                    maxY = Math.Max( maxY, bounds.Bottom );
        ////                    x = bounds.Right + FilmstripMargin;
        ////                    n++;
        ////                }
        ////            }
        ////            break;
        ////    }

        ////    if( animated == true )
        ////    {
        ////        Storyboard sb = animationBuilder.End();
        ////        sb.Begin();
        ////        sb.Completed += (object sender, EventArgs e)=>
        ////            {
        ////                 _multiScaleImageAspectRatio = GetAspectRatio(msi);
        ////            };

        ////    }
        ////    return new Rect( minX, minY, maxX - minX, maxY - minY );
        ////}

        /// <summary>
        /// use law of cosines to find out the increment of the angle
        /// a^2 = b^2+ c^2 - b*c*cos(alpha)
        /// returned in radians
        /// </summary>
        /// <param name="radius1">the radius</param>
        /// <param name="increment">the increment</param>
        /// <param name="boundingCircle">the bounding circle</param>
        /// <returns>the increment angle</returns>
        private static double GetIncrementAngle(double radius1, double increment, double boundingCircle)
        {
            const double Epsilon = 0.001;
            double radius2 = radius1 + increment;
            double bccosine = 4.0 * boundingCircle * boundingCircle - radius1 * radius1 - radius2 * radius2;
            double cosine = -bccosine / ((2.0 * radius1 * radius2) + Epsilon);
            if (Math.Abs(cosine) > 1.0)
            {
                return 0.0;
            }
            else
            {
                return Math.Acos(cosine);
            }
        }

        #endregion

        #region Hit Testing / Selection

        /// <summary>
        /// Hit test the multiscale image
        /// </summary>
        /// <param name="msi">the multiscale image</param>
        /// <param name="point">the point in the Silverlight app</param>
        /// <returns>the hit multi scale sub image</returns>
        public static MultiScaleSubImage HitTest(this MultiScaleImage msi, Point point)
        {
            Point logicalPoint = msi.ElementToLogicalPoint(point);
            foreach (MultiScaleSubImage subImage in msi.SubImages)
            {
                Rect subImageBounds = subImage.GetBounds();
                if (subImageBounds.Contains(logicalPoint) == true)
                {
                    return subImage;
                }
            }

            return null;
        }

        /// <summary>
        /// Is the sub image large?
        /// </summary>
        /// <param name="msi">the multi scale image</param>
        /// <param name="subImage">the sub image</param>
        /// <returns>true if the sub image is large on the screen</returns>
        public static bool IsSubImageLarge(this MultiScaleImage msi, MultiScaleSubImage subImage)
        {
            Rect viewBounds = msi.GetViewportBounds();
            Rect subImageBounds = subImage.GetBounds();
            subImageBounds.Intersect(viewBounds);
            double screenPixels = viewBounds.Width * viewBounds.Height;
            double imagePixels = subImageBounds.Width * subImageBounds.Height;
            double filled = imagePixels / screenPixels;
            if (filled > 0.3)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Determines if a SubImage is filling the view
        /// </summary>
        /// <param name="msi">the MultiScaleImage</param>
        /// <param name="subImage">the SubImage</param>
        /// <returns>true if the subimage is filling the 90% of the view, false otherwise.</returns>
        public static bool IsSubImageFillingView(this MultiScaleImage msi, MultiScaleSubImage subImage)
        {
            Rect viewBounds = msi.GetViewportBounds();
            Rect subImageBounds = subImage.GetBounds();
            subImageBounds.Intersect(viewBounds);
            double wr = subImageBounds.Width / viewBounds.Width;
            double hr = subImageBounds.Height / viewBounds.Height;
            if ((wr >= 0.9) && (hr >= 0.9))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Get the featured sub image
        /// </summary>
        /// <param name="msi">the multiscale image</param>
        /// <returns>the multi scale sub image</returns>
        public static MultiScaleSubImage GetFeaturedSubImage(this MultiScaleImage msi)
        {
            double bestArea = 0;
            MultiScaleSubImage bestSubImage = null;
            Rect viewBounds = msi.GetViewportBounds();

            foreach (MultiScaleSubImage subImage in msi.SubImages)
            {
                Rect subImageBounds = subImage.GetBounds();
                subImageBounds.Intersect(viewBounds);
                if ((subImageBounds.Width > 0) && (subImageBounds.Height > 0))
                {
                    double area = subImageBounds.Width * subImageBounds.Height;
                    if (area > bestArea)
                    {
                        bestArea = area;
                        bestSubImage = subImage;
                    }
                }
            }

            return bestSubImage;
        }

        /// <summary>
        /// Get the central subimage
        /// </summary>
        /// <param name="msi">the multiscale image</param>
        /// <returns>the multiscale subimage</returns>
        public static MultiScaleSubImage GetCentralSubImage(this MultiScaleImage msi)
        {
            double bestScore = -1;
            MultiScaleSubImage bestSubImage = null;
            Rect viewBounds = msi.GetViewportBounds();
            Point center = new Point(viewBounds.X + (viewBounds.Width / 2.0), viewBounds.Y + (viewBounds.Height / 2.0));

            foreach (MultiScaleSubImage subImage in msi.SubImages)
            {
                Rect subImageBounds = subImage.GetBounds();

                double upDiff = center.Y - subImageBounds.Bottom;
                double rightDiff = subImageBounds.Left - center.X;
                double downDiff = subImageBounds.Top - center.Y;
                double leftDiff = center.X - subImageBounds.Right;

                double diff = Math.Max(0.0, Math.Max(upDiff, downDiff)) + Math.Max(0.0, Math.Max(leftDiff, rightDiff));
                if (diff == 0)
                {
                    return subImage;
                }

                if ((bestScore == -1) || (diff < bestScore))
                {
                    bestScore = diff;
                    bestSubImage = subImage;
                }
            }

            return bestSubImage;
        }

        /// <summary>
        /// Get the sub image neighbors
        /// </summary>
        /// <param name="msi">the MultiScale Image</param>
        /// <param name="subImage">the sub image</param>
        /// <returns>the sub images's neighbors</returns>
        public static Neighbors GetSubImageNeighbors(this MultiScaleImage msi, MultiScaleSubImage subImage)
        {
            Neighbors n = new Neighbors();

            double bestUpScore = -1;
            double bestRightScore = -1;
            double bestDownScore = -1;
            double bestLeftScore = -1;

            Rect subImageRect = subImage.GetBounds();
            Point subImageCenter = new Point(subImageRect.X + (subImageRect.Width / 2.0), subImageRect.Y + (subImageRect.Height / 2.0));
            Size subImageHalfSize = new Size(subImageRect.Width / 2.0, subImageRect.Height / 2.0);
            if ((subImageHalfSize.Width == 0) || (subImageHalfSize.Height == 0))
            {
                return n;
            }

            foreach (MultiScaleSubImage otherImage in msi.SubImages)
            {
                if (otherImage == subImage)
                {
                    continue;
                }

                Rect otherRect = otherImage.GetBounds();

                // Up
                if ((subImageRect.Top > otherRect.Bottom) &&
                    ((otherRect.Right > subImageRect.Left) && (otherRect.Left < subImageRect.Right)))
                {
                    double upScore = subImageRect.Top - otherRect.Bottom;
                    if (otherRect.Left > subImageCenter.X)
                    {
                        upScore += (otherRect.Left - subImageCenter.X) / subImageHalfSize.Width;
                    }
                    else if (otherRect.Right < subImageCenter.X)
                    {
                        upScore += (subImageCenter.X - otherRect.Left) / subImageHalfSize.Width;
                    }

                    if ((bestUpScore == -1) || (upScore < bestUpScore))
                    {
                        bestUpScore = upScore;
                        n.Up = otherImage;
                    }
                }

                // Right
                if ((otherRect.Left > subImageRect.Right) &&
                    ((otherRect.Bottom > subImageRect.Top) && (otherRect.Top < subImageRect.Bottom)))
                {
                    double rightScore = otherRect.Left - subImageRect.Right;
                    if (otherRect.Top > subImageCenter.Y)
                    {
                        rightScore += (otherRect.Top - subImageCenter.Y) / subImageHalfSize.Height;
                    }
                    else if (otherRect.Bottom < subImageCenter.Y)
                    {
                        rightScore += (subImageCenter.Y - otherRect.Bottom) / subImageHalfSize.Height;
                    }

                    if (bestRightScore == -1 || rightScore < bestRightScore)
                    {
                        bestRightScore = rightScore;
                        n.Right = otherImage;
                    }
                }

                // Down
                if ((otherRect.Top > subImageRect.Bottom) &&
                    ((otherRect.Right > subImageRect.Left) && (otherRect.Left < subImageRect.Right)))
                {
                    double downScore = otherRect.Top - subImageRect.Bottom;
                    if (otherRect.Left > subImageCenter.X)
                    {
                        downScore += (otherRect.Left - subImageCenter.X) / subImageHalfSize.Width;
                    }
                    else if (otherRect.Right < subImageCenter.X)
                    {
                        downScore += (subImageCenter.X - otherRect.Right) / subImageHalfSize.Width;
                    }

                    if (bestDownScore == -1 || downScore < bestDownScore)
                    {
                        bestDownScore = downScore;
                        n.Down = otherImage;
                    }
                }

                // Left
                if ((subImageRect.Left > otherRect.Right) &&
                    ((otherRect.Bottom > subImageRect.Top) && (otherRect.Top < subImageRect.Bottom)))
                {
                    double leftScore = subImageRect.Left - otherRect.Right;
                    if (otherRect.Top > subImageCenter.Y)
                    {
                        leftScore += (otherRect.Top - subImageCenter.Y) / subImageHalfSize.Height;
                    }
                    else if (otherRect.Bottom < subImageCenter.Y)
                    {
                        leftScore += (subImageCenter.Y - otherRect.Bottom) / subImageHalfSize.Height;
                    }

                    if (bestLeftScore == -1 || leftScore < bestLeftScore)
                    {
                        bestLeftScore = leftScore;
                        n.Left = otherImage;
                    }
                }
            }

            return n;
        }

        #endregion

        /// <summary>
        /// Implements an "optimal" animated zoom/pan path between two view rectangles.
        /// </summary>
        /// <remarks>
        /// Based on the paper "Smooth and efficient zooming and panning" by Jarke j. van Wijk and Wim A.A. Nuij
        /// </remarks>
        private class ViewportAnimation : DependencyObject
        {
            #region Fields
            public static readonly DependencyProperty ProgressProperty = DependencyProperty.RegisterAttached("Progress", typeof(double), typeof(ViewportAnimation), new PropertyMetadata(0.0, new PropertyChangedCallback(delegate(DependencyObject o, DependencyPropertyChangedEventArgs e) { ((ViewportAnimation)o).ProgressChanged(e); })));

            // Rho controls the amount of zooming used.  sqrt(2) is an empirically determined value suggested in the paper.
            private static readonly double p = Math.Sqrt(2);

            // average speed of motion in units of (size of viewport)/second.
            private static readonly double velocity = 1;

            // c0 and c1 are the positions of the center of the viewport at the beginning and end of the animation, respectively.
            // direction = c1 - c0.
            private Point c0, c1, direction;

            // w is the width of the viewport.
            // u is the distance the center of the viewport has traveled.
            // b and r are constants used in the paper's equations.
            // S is the perceptual distance traveled in units of (size of viewport), including the effects of zooming.
            private double w0, w1, u0, u1, b0, b1, r0, r1, S;
            private MultiScaleImage multiScaleImage;
            #endregion

            #region Constructors
            /// <summary>
            /// Initializes a new instance of the ViewportAnimation class.
            /// </summary>
            /// <param name="multiScaleImage">the multiscale image</param>
            /// <param name="newViewportBounds">the new viewport bounds</param>
            public ViewportAnimation(MultiScaleImage multiScaleImage, Rect newViewportBounds)
            {
                this.multiScaleImage = multiScaleImage;

                // Calculate all the constants for the equations given in the paper.
                newViewportBounds = multiScaleImage.CenterRectInViewport(newViewportBounds);
                Rect initialViewportBounds = new Rect(multiScaleImage.ViewportOrigin, new Point(multiScaleImage.ViewportOrigin.X + multiScaleImage.ViewportWidth, multiScaleImage.ViewportOrigin.Y + multiScaleImage.ViewportWidth * multiScaleImage.ActualHeight / multiScaleImage.ActualWidth));

                this.c0 = new Point(
                    initialViewportBounds.Left + initialViewportBounds.Width / 2,
                    initialViewportBounds.Top + initialViewportBounds.Height / 2);

                this.c1 = new Point(
                    newViewportBounds.Left + newViewportBounds.Width / 2,
                    newViewportBounds.Top + newViewportBounds.Height / 2);

                this.direction = new Point(this.c1.X - this.c0.X, this.c1.Y - this.c0.Y);

                this.w0 = initialViewportBounds.Width;
                this.w1 = newViewportBounds.Width;

                this.u0 = 0;
                this.u1 = Math.Sqrt(this.direction.X * this.direction.X + this.direction.Y * this.direction.Y);

                // See page 5 of the paper for these equations.
                this.b0 = (this.w1 * this.w1 - this.w0 * this.w0 + p * p * p * p * this.u1 * this.u1) / (2 * this.w0 * p * p * this.u1);
                this.b1 = (this.w1 * this.w1 - this.w0 * this.w0 - p * p * p * p * this.u1 * this.u1) / (2 * this.w1 * p * p * this.u1);

                this.r0 = Math.Log(-this.b0 + Math.Sqrt(this.b0 * this.b0 + 1));
                this.r1 = Math.Log(-this.b1 + Math.Sqrt(this.b1 * this.b1 + 1));

                this.S = (this.r1 - this.r0) / p;
                if (double.IsNaN(this.S))
                {
                    return;
                }

                // Construct a Silverlight storyboard to drive the animation.
                // The storyboard will drive the Progress property of this object from 0 to 1,
                // which will in turn set ViewportWidth and ViewportOrigin appropriately.
                // Unlike the paper, we use a spline to accelerate and decelerate instead of 
                // maintaining a constant velocity.
                TimeSpan duration = TimeSpan.FromSeconds(this.S / velocity);
                DoubleAnimationUsingKeyFrames da = new DoubleAnimationUsingKeyFrames();
                ////da.Duration = new Duration(duration);
                SplineDoubleKeyFrame kf = new SplineDoubleKeyFrame();
                kf.KeySpline.ControlPoint1 = new Point(.7, 1);
                kf.KeySpline.ControlPoint2 = new Point(.7, 1);
                kf.Value = 1;
                kf.KeyTime = KeyTime.FromTimeSpan(duration);
                da.KeyFrames.Add(kf);
                Storyboard.SetTarget(da, this);
                Storyboard.SetTargetProperty(da, new PropertyPath(ProgressProperty));
                Storyboard sb = new Storyboard();
                sb.Children.Add(da);
                sb.Completed += delegate(object o, EventArgs e) { multiScaleImage.UseSprings = true; };
                multiScaleImage.UseSprings = false;
                sb.Begin();
            }
            #endregion

            #region Properties
            /// <summary>
            /// Gets or sets the progress
            /// </summary>
            public double Progress 
            { 
                get 
                { 
                    return (double)GetValue(ProgressProperty); 
                } 
                
                set 
                { 
                    SetValue(ProgressProperty, value); 
                } 
            }
            #endregion

            #region Methods

            /// <summary>
            /// Progress changed event handler
            /// </summary>
            /// <param name="e"></param>
            public void ProgressChanged(DependencyPropertyChangedEventArgs e)
            {
                // See page 5 of the paper for these equations.
                // s goes from 0 to S as Progress goes from 0 to 1
                double s = ((double)e.NewValue) * this.S;

                // Now compute u(s) and w(s)
                double u = this.w0 / (p * p) * Math.Cosh(this.r0) * Math.Tanh(p * s + this.r0) - this.w0 / (p * p) * Math.Sinh(this.r0) + this.u0;
                double w = this.w0 * Math.Cosh(this.r0) / Math.Cosh(p * s + this.r0);

                this.multiScaleImage.ViewportWidth = w;
                this.multiScaleImage.SetViewportCenter(new Point(this.c0.X + this.direction.X * u / this.u1, this.c0.Y + this.direction.Y * u / this.u1));
            }

            #endregion
        }
    }
}
